using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Net;

namespace Game
{
    public class Unit
    {
        public enum UnitType
        {
            ATTACKER, DEFENDER, ECON
        }

        public Unit(Rectangle a_base, UnitType type)
        {
            m_pos = a_base.Center;
            m_type = type;
        }

        // returns true when we've arrived
        public bool MoveTo(Point dest)
        {
            int diff_x = (dest.X - m_pos.X);
            int diff_y = (dest.Y - m_pos.Y);

            if (Math.Abs(diff_x) <= 1 && Math.Abs(diff_y) <= 1)
                return true;

            // not close enough to attack, get closer. Pick a random axis to move on.
            if (diff_x != 0 && (diff_y == 0 || ((diff_x + diff_y) & 0x1) != 0))
                m_pos.X += Math.Sign(diff_x);
            else
                m_pos.Y += Math.Sign(diff_y);

            return false;
        }

        public void Update(Player a_owner, Player a_enemy)      // returns whether we died and should be removed from the unit list
        {
            if (m_dead)     // we'll get removed next update...
                return;

            if (!m_attacking)
            {
                if (a_owner.m_base.Contains(m_pos))
                {
                    m_pos.X += a_owner.m_random.Next(-1, 2);
                    m_pos.Y += a_owner.m_random.Next(-1, 2);
                }
                else
                {
                    MoveTo(a_owner.m_base.Center);
                }
            }
            else if (a_enemy.m_units.Count > 0)
            {
                // we're attacking. Make sure we have a unit to attack, then go attack him.
                if (m_target == null || m_target.m_dead)
                    m_target = a_enemy.m_units[a_owner.m_random.Next(a_enemy.m_units.Count)];

                if (MoveTo(m_target.m_pos))     // returns true if we're close enough to attack
                {
                    // we're close enough to attack, resolve the combat based on the victim's type.
                    double luck = a_owner.m_random.NextDouble();
                    bool win = (m_target.m_type == UnitType.DEFENDER && luck > 0.6) ||      // usually lose to defenders
                               (m_target.m_type == UnitType.ATTACKER && luck > 0.4) ||      // usually win against other attackers
                               (m_target.m_type == UnitType.ECON && luck > 0.1);            // slaughter econs

                    m_target.m_dead = win;
                    m_dead = !win;
                }
            }
        }

        public UnitType m_type;
        public Point m_pos;
        public Unit m_target = null;
        public bool m_attacking = false;
        public bool m_dead = false;
    }

    [Serializable]
    public struct PlayerInput
    {
        public void Init()
        {
            m_frame_start = -1;     // what range of game-world-updates this input is valid for
            m_frame_end = -1;

            m_attack = false;       // user input
            m_scout = false;
            m_off_perc = 33;
            m_def_perc = 33;
            m_econ_perc = 34;
        }

        public int m_frame_start;
        public int m_frame_end;

        public bool m_attack;
        public bool m_scout;
        
        public int m_off_perc;
        public int m_def_perc;
        public int m_econ_perc;
    }

    public class Player
    {
        public Player(Rectangle a_base, int seed)
        {
            m_base = a_base;
            m_orig_base = m_base;

            m_random = new Random(seed);

            m_curr_input.Init();
            m_sent_input.Init();
            m_unsent_input.Init();

            m_curr_input.m_frame_start = 0;
            m_curr_input.m_frame_end = ODE.UPDATE_RATE;
            m_sent_input.m_frame_start = ODE.UPDATE_RATE;
            m_sent_input.m_frame_end = 2 * ODE.UPDATE_RATE;
            m_unsent_input.m_frame_start = 2 * ODE.UPDATE_RATE;
            m_unsent_input.m_frame_end = 3 * ODE.UPDATE_RATE;

            m_remote_inputs.Add(m_curr_input);
            m_remote_inputs.Add(m_sent_input);
        }

        public bool CanUpdate()
        {
            return m_frame < m_curr_input.m_frame_end;
        }

        // todo: make the endgame less ridiculous
        public static int OFF_UNIT_CAP = 500;
        public static int DEF_UNIT_CAP = 500;
        public static int ECON_UNIT_CAP = 500;
        
        public void Update(GameTime gameTime, Player a_enemy, bool is_remote)
        {
            ++m_frame;

            float secs = gameTime.ElapsedGameTime.Milliseconds / 1000.0f;
            float newMoney = secs * GetDollarsPerSec();

            // update unit production
            if (GetNumOfType(Unit.UnitType.ATTACKER) < OFF_UNIT_CAP)
                off_unit_count += newMoney * off_frac;

            if (GetNumOfType(Unit.UnitType.DEFENDER) < DEF_UNIT_CAP)
                def_unit_count += newMoney * def_frac;

            if (GetNumOfType(Unit.UnitType.ECON) < ECON_UNIT_CAP)
                econ_unit_count += newMoney * econ_frac;            
            

            UpdateCreation(ref off_unit_count, Unit.UnitType.ATTACKER);
            UpdateCreation(ref def_unit_count, Unit.UnitType.DEFENDER);
            UpdateCreation(ref econ_unit_count, Unit.UnitType.ECON);

            // resize our base depending on how many units we have
            int inflate = (int) (100 * m_units.Count / (float)(OFF_UNIT_CAP + DEF_UNIT_CAP + ECON_UNIT_CAP));
            m_base = m_orig_base;
            m_base.Inflate(inflate, inflate);

            // update orders
            if (m_curr_input.m_attack)
            {
                foreach (Unit u in m_units.FindAll(u => u.m_type == Unit.UnitType.ATTACKER))
                    u.m_attacking = true;
            }

            if (m_curr_input.m_scout)
            {
                foreach (Unit u in m_units.FindAll(u => u.m_type == Unit.UnitType.ATTACKER && !u.m_attacking).Take(5))
                    u.m_attacking = true;
            }

            // update each unit
            foreach (Unit u in m_units)
                u.Update(this, a_enemy);

            // remove the dead ones
            m_units.RemoveAll(u => u.m_dead);
        }

        List<PlayerInput> m_remote_inputs = new List<PlayerInput>();

        public void NetUpdate(PacketWriter writer, PacketReader reader, NetworkSession session, bool is_remote)
        {
            if (ODE.TEST_SINGLEPLAYER)
            {
                m_curr_input = m_unsent_input;
                m_unsent_input.m_attack = false;
                m_unsent_input.m_scout = false;
                return;
            }

            if (is_remote)
            {
                LocalNetworkGamer gamer = session.LocalGamers[0];

                if (gamer.IsDataAvailable)       // just read one packet, we'll process it and then read any more packets on the next update.
                {
                    NetworkGamer sender;
                    gamer.ReceiveData(reader, out sender);      // Read a single packet.

                    if (!sender.IsLocal)
                    {
                        byte[] buffer = new byte[reader.Length];
                        reader.Read(buffer, 0, reader.Length);

                        MemoryStream memStream = new MemoryStream();
                        memStream.Write(buffer, 0, reader.Length);
                        memStream.Seek(0, SeekOrigin.Begin);

                        BinaryFormatter formatter = new BinaryFormatter();
                        m_remote_inputs.Add((PlayerInput)formatter.Deserialize(memStream));
                    }
                }

                // has our input gone stale? switch to the next one 
                if (m_remote_inputs.Count != 0 &&
                    m_remote_inputs[0].m_frame_start <= m_frame && m_frame < m_remote_inputs[0].m_frame_end)
                {
                    m_curr_input = m_remote_inputs[0];
                    m_remote_inputs.RemoveAt(0);
                }
            }
            else if (m_last_sent_update != m_frame && m_frame % ODE.UPDATE_RATE == 0)        // send data every couple frames
            {
                m_last_sent_update = m_frame;

                // shuffle our cached inputs up
                m_curr_input = m_sent_input;
                m_sent_input = m_unsent_input;

                MemoryStream memStream = new MemoryStream();
                BinaryFormatter formatter = new BinaryFormatter();
                formatter.Serialize(memStream, m_sent_input);

                byte[] output = memStream.ToArray();
                writer.Write(output);

                foreach (LocalNetworkGamer gamer in session.LocalGamers)
                {
                    gamer.SendData(writer, SendDataOptions.ReliableInOrder);
                }

                // reset the input state
                m_unsent_input.m_attack = false;
                m_unsent_input.m_scout = false;
                m_unsent_input.m_frame_start = m_frame + 2 * ODE.UPDATE_RATE;
                m_unsent_input.m_frame_end = m_frame + 3 * ODE.UPDATE_RATE;              
            }

        }

        public void Draw(SpriteBatch batch, Texture2D sword, Texture2D shield, Texture2D dollar)
        {
            foreach (Unit u in m_units)
            {
                Color color = Color.Black;
                Texture2D tex = null;
                switch (u.m_type)
                {
                    case Unit.UnitType.ATTACKER: 
                        color = Color.Pink; 
                        tex = sword;
                        break;

                    case Unit.UnitType.DEFENDER: 
                        color = Color.LightBlue; 
                        tex = shield;
                        break;

                    case Unit.UnitType.ECON: 
                        color = Color.LightGreen; 
                        tex = dollar;
                        break;
                }

                Rectangle rect = new Rectangle(u.m_pos.X - 8, u.m_pos.Y - 8, 16, 16);
                batch.Draw(tex, rect, color);
            }
        }

        const int SIGHT_RADIUS = 40;

        public void DrawFogPass(SpriteBatch batch, Texture2D fog, int off_x, int off_y)
        {
            off_x -= SIGHT_RADIUS;
            off_y -= SIGHT_RADIUS;

            foreach (Unit u in m_units)
            {
                
                Rectangle rect = new Rectangle(u.m_pos.X + off_x, u.m_pos.Y + off_y, 2*SIGHT_RADIUS, 2*SIGHT_RADIUS);
                batch.Draw(fog, rect, Color.White);
            }
        }

        public int GetNumOfType(Unit.UnitType type)
        {
            return (from u in m_units where u.m_type == type select u).Count();
        }

        static float ECON_UNITS_TO_DOLLARS_PER_SEC = 0.1f;
        public float GetDollarsPerSec()
        {
            return ECON_UNITS_TO_DOLLARS_PER_SEC * GetNumOfType(Unit.UnitType.ECON);
        }

        public void UpdateCreation(ref float a_targetCount, Unit.UnitType a_type)
        {
            while (a_targetCount > 1.0f)
            {
                m_units.Add(new Unit(m_base, a_type));
                a_targetCount -= 1.0f;
            }
        }

        // unit positions
        public Rectangle m_base;
        public Rectangle m_orig_base;

        public List<Unit> m_units = new List<Unit>();
        public Random m_random;

        // current target count of units
        public float off_unit_count = 10.0f;
        public float def_unit_count = 10.0f;
        public float econ_unit_count = 10.0f;

        public float off_frac { get { return m_curr_input.m_off_perc / 100.0f; } }
        public float def_frac { get { return m_curr_input.m_def_perc / 100.0f; } }
        public float econ_frac { get { return m_curr_input.m_econ_perc / 100.0f; } }

        public float next_off_frac { get { return m_unsent_input.m_off_perc / 100.0f; } }
        public float next_def_frac { get { return m_unsent_input.m_def_perc / 100.0f; } }
        public float next_econ_frac { get { return m_unsent_input.m_econ_perc / 100.0f; } }

        public int m_frame = 0;
        public int m_last_sent_update = -1;

        PlayerInput m_curr_input;       // current values
        public PlayerInput m_sent_input;       // waiting to apply
        public PlayerInput m_unsent_input;     // mutable
    }
    


    public class ODE : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch m_spriteBatch;
        SpriteFont spriteFont;

        NetworkSession session;

        PacketReader packetReader = new PacketReader();
        PacketWriter packetWriter = new PacketWriter();

        public ODE()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";

            graphics.PreferredBackBufferHeight = 480;
            graphics.PreferredBackBufferWidth = 1080;

            Components.Add(new GamerServicesComponent(this));

            IsMouseVisible = true;
        }

        BasicEffect m_basic_effect;
        VertexDeclaration m_VertPosColDecl;

        Texture2D m_TexSword;
        Texture2D m_TexShield;
        Texture2D m_TexDollar;

        Texture2D m_TexFog;
        
        Texture2D m_TexButton;
        Texture2D m_TexMap;

        RenderTarget2D renderTarget;

        VertexPositionColor[] m_TriVerts;
        Vector3 m_TriTop;
        Vector3 m_TriLeft;
        Vector3 m_TriRight;
        Rectangle m_MapRect;

        public static bool TEST_SINGLEPLAYER = false; 

        protected override void Initialize()
        {
            base.Initialize();

            PresentationParameters pp = GraphicsDevice.PresentationParameters;
            int panel_width = 440;
            m_MapRect = new Rectangle(panel_width, 5, pp.BackBufferWidth - panel_width - 5, pp.BackBufferHeight - 10);

            m_basic_effect = new BasicEffect(GraphicsDevice, null);
            m_basic_effect.VertexColorEnabled = true;
            m_basic_effect.View = Matrix.Identity;
            m_basic_effect.World = Matrix.Identity;
            m_basic_effect.Projection = Matrix.CreateOrthographicOffCenter(0, graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight, 0, 0, 1);

            m_VertPosColDecl = new VertexDeclaration(GraphicsDevice, VertexPositionColor.VertexElements);

            int triSize = 260;
            m_TriTop = new Vector3(panel_width / 2,             140, 0);
            m_TriLeft = new Vector3(m_TriTop.X - triSize / 2,   m_TriTop.Y + 2 * triSize / 3, 0);
            m_TriRight = new Vector3(m_TriTop.X + triSize / 2,  m_TriTop.Y + 2 * triSize / 3, 0);

            m_TriVerts = new VertexPositionColor[3];
            m_TriVerts[0].Position = m_TriTop;
            m_TriVerts[0].Color = Color.LightPink;
            m_TriVerts[1].Position = m_TriLeft;
            m_TriVerts[1].Color = Color.CornflowerBlue;
            m_TriVerts[2].Position = m_TriRight;
            m_TriVerts[2].Color = Color.LightGreen;

            m_attack_rect = new Rectangle((int)m_TriLeft.X - 50, (int)m_TriTop.Y - 25, 100, 25);
            m_scout_rect = new Rectangle((int)m_TriRight.X - 50, (int)m_TriTop.Y - 25, 100, 25);
            m_new_game_rect = new Rectangle(400, 230, 300, 30);

            renderTarget = new RenderTarget2D(GraphicsDevice, m_MapRect.Width, m_MapRect.Height, 1, SurfaceFormat.Color, RenderTargetUsage.PreserveContents);

            if (TEST_SINGLEPLAYER)
            {
                StartGame();
            }
            else
            {
                // start kicking off our network stuff
                StartSignIn();
                SignedInGamer.SignedOut += new EventHandler<SignedOutEventArgs>(SignedInGamer_SignedOut);
            }
        }

        protected override void LoadContent()
        {
            m_spriteBatch = new SpriteBatch(GraphicsDevice);

            spriteFont = Content.Load<SpriteFont>("sheet");

            m_TexSword = Content.Load<Texture2D>("sword");
            m_TexShield = Content.Load<Texture2D>("shield");
            m_TexDollar = Content.Load<Texture2D>("dollar");

            m_TexFog = Content.Load<Texture2D>("fog");

            m_TexButton = Content.Load<Texture2D>("button");
            m_TexMap = Content.Load<Texture2D>("map");
        }

        protected override void UnloadContent()
        {
        }

        void SignedInGamer_SignedOut(object sender, SignedOutEventArgs e)
        {
            ResetGame();
        }

        void session_GamerLeft(object sender, GamerLeftEventArgs e)
        {
            ResetGame();
        }

        void ResetGame()
        {
            me = null;
            enemy = null;

            if (!TEST_SINGLEPLAYER)
            {
                session.Dispose();
                session = null;

                StartSignIn();
            }
            else
            {
                StartGame();
            }
        }

        void StartSignIn()
        {
            if (Gamer.SignedInGamers.Count == 0)
            {
                SignedInGamer.SignedIn += new EventHandler<SignedInEventArgs>(SignedInGamer_SignedIn);
                Guide.ShowSignIn(1, false);
            }
            else
            {
                StartSession();
            }
        }

        void SignedInGamer_SignedIn(object sender, SignedInEventArgs e)
        {
            SignedInGamer.SignedIn -= SignedInGamer_SignedIn;
            StartSession();
        }

        void StartSession()
        {
            AvailableNetworkSessionCollection availableSessions = NetworkSession.Find(NetworkSessionType.SystemLink, 1, null);
            if (availableSessions.Count > 0)
            {
                session = NetworkSession.Join(availableSessions[0]);
            }
            else
            {
                session = NetworkSession.Create(NetworkSessionType.SystemLink, 1, 2);
                session.GamerJoined += new EventHandler<GamerJoinedEventArgs>(session_GamerJoined);
            }

            session.GameStarted += new EventHandler<GameStartedEventArgs>(session_GameStarted);
            session.GamerLeft += new EventHandler<GamerLeftEventArgs>(session_GamerLeft);
        }

        int m_gameCreationSeed = 0;

        void session_GamerJoined(object sender, GamerJoinedEventArgs e)
        {
            // send the game creation data
            if (session.AllGamers.Count == 2 && session.IsHost)
            {
                session.LocalGamers[0].IsReady = true;

                m_gameCreationSeed = new Random().Next();
                packetWriter.Write(m_gameCreationSeed);
                session.LocalGamers[0].SendData(packetWriter, SendDataOptions.ReliableInOrder);
            }
        }

        void session_GameStarted(object sender, GameStartedEventArgs e)
        {
            StartGame();
        }

        void StartGame()
        {
            int base_size = 50;
            int growth_amt = 100;

            int top_off = growth_amt;
            int bot_off = growth_amt + base_size;

            Rectangle base_top = new Rectangle(m_MapRect.X + top_off, m_MapRect.Y + top_off,            base_size, base_size);
            Rectangle base_bot = new Rectangle(m_MapRect.Right - bot_off, m_MapRect.Bottom - bot_off,   base_size, base_size);

            if (TEST_SINGLEPLAYER || session.IsHost)
            {
                me = new Player(base_top, m_gameCreationSeed);
                enemy = new Player(base_bot, m_gameCreationSeed + 1);
            }
            else
            {
                enemy = new Player(base_top, m_gameCreationSeed);
                me = new Player(base_bot, m_gameCreationSeed + 1);
            }
        }

        Player me;
        Player enemy;

        static public int UPDATE_RATE = 15;     // updates per net packet

        protected override void Update(GameTime gameTime)
        {
            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

            base.Update(gameTime);

            if (!TEST_SINGLEPLAYER)
            {
                if (session == null)        // wait for net setup
                    return;

                session.Update();

                // lobby update. Wait for the seed, then we're good to go.
                if (session.SessionState == NetworkSessionState.Lobby)
                {
                    LocalNetworkGamer gamer = session.LocalGamers[0];

                    if (session.IsHost && session.IsEveryoneReady)
                    {
                        session.StartGame();
                    }
                    else if (!session.IsHost && gamer.IsDataAvailable)
                    {
                        NetworkGamer sender;
                        gamer.ReceiveData(packetReader, out sender);
                        m_gameCreationSeed = packetReader.ReadInt32();
                        session.LocalGamers[0].IsReady = true;
                    }

                    return;
                }
            }

            if (me.m_frame > 0 && (me.m_units.Count == 0 || enemy.m_units.Count == 0))
            {
                MouseState mouse = Mouse.GetState();
                Point click = new Point(mouse.X, mouse.Y);

                if (mouse.LeftButton == ButtonState.Pressed && m_new_game_rect.Contains(click))
                    ResetGame();

                return;
            }

            // read user input
            if (IsActive)
            {
                TestButtons();
                GetProduction();
            }

            me.NetUpdate(packetWriter, packetReader, session, false);
            enemy.NetUpdate(packetWriter, packetReader, session, true);

            if (TEST_SINGLEPLAYER || (me.CanUpdate() && enemy.CanUpdate()))
            {
                if (TEST_SINGLEPLAYER || session.IsHost)
                {
                    me.Update(gameTime, enemy, false);
                    enemy.Update(gameTime, me, true);
                }
                else
                {
                    enemy.Update(gameTime, me, true); 
                    me.Update(gameTime, enemy, false);
                }
            }
        }

        // buttons
        Rectangle m_attack_rect;
        Rectangle m_scout_rect;
        Rectangle m_new_game_rect;

        private void TestButtons()
        {
            MouseState mouse = Mouse.GetState();
            Point click = new Point(mouse.X, mouse.Y);
            if (mouse.LeftButton == ButtonState.Pressed)
            {
                if (m_attack_rect.Contains(click))
                    me.m_unsent_input.m_attack = true;

                if (m_scout_rect.Contains(click))
                    me.m_unsent_input.m_scout = true;
            }
        }

        private void GetProduction()
        {
            // get new production rules,
            // TODO: cache and apply with pre-defined delays, wait for messages to do updates.
            MouseState mouse = Mouse.GetState();
            if (mouse.LeftButton == ButtonState.Pressed)
            {
                // barycentric coords...
                // http://steve.hollasch.net/cgindex/math/barycentric.html
                float x0 = mouse.X;
                float y0 = mouse.Y;
                float x1 = m_TriTop.X;
                float y1 = m_TriTop.Y;
                float x2 = m_TriLeft.X;
                float y2 = m_TriLeft.Y;
                float x3 = m_TriRight.X;
                float y3 = m_TriRight.Y;
                float b0 = (x2 - x1) * (y3 - y1) - (x3 - x1) * (y2 - y1);
                float b1 = ((x2 - x0) * (y3 - y0) - (x3 - x0) * (y2 - y0)) / b0;
                float b2 = ((x3 - x0) * (y1 - y0) - (x1 - x0) * (y3 - y0)) / b0;
                float b3 = ((x1 - x0) * (y2 - y0) - (x2 - x0) * (y1 - y0)) / b0;

                if (-0.1 <= b1 && b1 <= 1.1f &&
                    -0.1 <= b2 && b2 <= 1.1f &&
                    -0.1 <= b3 && b3 <= 1.1f)
                {
                    // we accept input out of bounds, and renormalize them back into bounds
                    me.m_unsent_input.m_off_perc = Math.Min(100, Math.Max(0, (int) (b1 * 100.0f)));
                    me.m_unsent_input.m_def_perc = Math.Min(100, Math.Max(0, (int) (b2 * 100.0f)));

                    me.m_unsent_input.m_econ_perc = Math.Min(100, Math.Max(0, 100 - me.m_unsent_input.m_off_perc - me.m_unsent_input.m_def_perc));
                    me.m_unsent_input.m_off_perc = Math.Min(100, Math.Max(0, 100 - me.m_unsent_input.m_econ_perc - me.m_unsent_input.m_def_perc));
                    me.m_unsent_input.m_def_perc = Math.Min(100, Math.Max(0, 100 - me.m_unsent_input.m_off_perc - me.m_unsent_input.m_econ_perc));

                }
            }
        }

        protected override void Draw(GameTime gameTime)
        {
            base.Draw(gameTime);

            GraphicsDevice.RenderState.CullMode = CullMode.None;
            GraphicsDevice.VertexDeclaration = m_VertPosColDecl;

            if (me == null || enemy == null || (!TEST_SINGLEPLAYER && session == null))
            {
                DrawLobby();
                return;
            }

            Texture2D fog_map = DrawFogBlendMap();      // the fog overlay, as a rendertarget

            DrawPreFog();       // the background (map and units)

            BlendFogMap(fog_map);   // obscure the map

            DrawPostFog();      // the UI
        }

        // Fog render-to-texture: black = fogged, white = visible
        private Texture2D DrawFogBlendMap()
        {
            GraphicsDevice.SetRenderTarget(0, renderTarget);
            GraphicsDevice.Clear(Color.Black);

            m_basic_effect.Begin(SaveStateMode.SaveState);
            m_basic_effect.CurrentTechnique.Passes[0].Begin();
            m_spriteBatch.Begin(SpriteBlendMode.None, SpriteSortMode.Immediate, SaveStateMode.SaveState);
            
            m_spriteBatch.Draw(m_TexMap, new Rectangle(0, 0, m_MapRect.Width, m_MapRect.Height), new Color(60, 60, 60));

            GraphicsDevice.RenderState.AlphaBlendEnable = true;
            GraphicsDevice.RenderState.SourceBlend = Blend.SourceAlpha;
            GraphicsDevice.RenderState.DestinationBlend = Blend.One;
            GraphicsDevice.RenderState.BlendFunction = BlendFunction.Add;

            me.DrawFogPass(m_spriteBatch, m_TexFog, -m_MapRect.X, -m_MapRect.Y);

            m_spriteBatch.End();
            m_basic_effect.CurrentTechnique.Passes[0].End();
            m_basic_effect.End();

            // get the fog map as a texture
            GraphicsDevice.SetRenderTarget(0, null);
            return renderTarget.GetTexture();
        }

        private void DrawPreFog()
        {
            GraphicsDevice.VertexDeclaration = m_VertPosColDecl;
            GraphicsDevice.Clear(new Color(30, 30, 30));

            m_basic_effect.Begin(SaveStateMode.SaveState);
            m_basic_effect.CurrentTechnique.Passes[0].Begin();
            m_spriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Deferred, SaveStateMode.SaveState);

            // draw the map
            m_spriteBatch.Draw(m_TexMap, m_MapRect, Color.White);

            // draw the units
            enemy.Draw(m_spriteBatch, m_TexSword, m_TexShield, m_TexDollar);
            me.Draw(m_spriteBatch, m_TexSword, m_TexShield, m_TexDollar);

            m_spriteBatch.End();
            m_basic_effect.CurrentTechnique.Passes[0].End();
            m_basic_effect.End();
        }

        private void BlendFogMap(Texture2D fog_map)
        {
            // hax: show the fog map, using the sprite batch because I'm lazy!
            m_spriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Immediate, SaveStateMode.SaveState);

            m_basic_effect.Begin(SaveStateMode.SaveState);
            m_basic_effect.CurrentTechnique.Passes[0].Begin();

            GraphicsDevice.RenderState.AlphaBlendEnable = true;
            GraphicsDevice.RenderState.SourceBlend = Blend.InverseSourceAlpha;
            GraphicsDevice.RenderState.DestinationBlend = Blend.One;
            GraphicsDevice.RenderState.BlendFunction = BlendFunction.Min;

            m_spriteBatch.Draw(fog_map, m_MapRect, Color.White);

            m_basic_effect.CurrentTechnique.Passes[0].End();
            m_basic_effect.End();

            m_spriteBatch.End();
        }

        private void DrawPostFog()
        {
            m_basic_effect.Begin(SaveStateMode.SaveState);
            m_basic_effect.CurrentTechnique.Passes[0].Begin();
            m_spriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Deferred, SaveStateMode.SaveState);

            DrawProductionTriangle();
            DrawUI();

            m_spriteBatch.End();
            m_basic_effect.CurrentTechnique.Passes[0].End();
            m_basic_effect.End();
        }

        private void DrawLobby()
        {
            m_spriteBatch.Begin();
            string msg = "";
            if (Guide.IsVisible)
                msg = "";
            else if (session != null && session.AllGamers.Count == 1)
                msg = "Hosting, waiting for players to join...";
            else if (session != null && !session.IsEveryoneReady)
                msg = "Hosting, waiting for players to ready...";

            m_spriteBatch.DrawString(spriteFont, msg, new Vector2(400, 400), Color.White);
            m_spriteBatch.End();
        }

        void DrawButton(Rectangle rect, Color color, SpriteBatch batch, string text)
        {
            batch.Draw(m_TexButton, rect, color);
            m_spriteBatch.DrawString(spriteFont, text, new Vector2(rect.X + 20, Math.Min(rect.Y + 10, rect.Center.Y - 13)), Color.White);
        }

        private void DrawUI()
        {
            Color attack_color = new Color(20, 20, 20);
            Color scout_color = new Color(20, 20, 20);
            if (me.m_unsent_input.m_attack)
                attack_color = new Color(40, 40, 40);
            if (me.m_unsent_input.m_scout)
                scout_color = new Color(40, 40, 40);

            DrawButton(m_scout_rect, scout_color, m_spriteBatch, "Scout!");
            DrawButton(m_attack_rect, attack_color, m_spriteBatch, "Attack!");

            // help text
            m_spriteBatch.DrawString(spriteFont, "Your current unit count of each type is shown,\n" +
                                               "    along with the current production speed.\n" +
                                               "Click on the triangle to reallocate production.",
                                        new Vector2(20, m_TriLeft.Y + 90), Color.WhiteSmoke);

            // draw victory/loss text
            if (me.m_frame > 0 && (enemy.m_units.Count == 0 || me.m_units.Count == 0))
            {
                string win = "You are the best at this game! Yay!";
                string lose = "You are not quite the best...   ;_;";
                string newgame = "                New Game";

                string msg = win;
                if (me.m_units.Count == 0)
                    msg = lose;

                Rectangle msg_rect = m_new_game_rect;
                msg_rect.Y -= (int)(1.5 * m_new_game_rect.Height);
                msg_rect.Height = 3 * m_new_game_rect.Height;
                msg_rect.X = m_new_game_rect.X - 20;
                msg_rect.Width = m_new_game_rect.Width + 40;

                DrawButton(msg_rect, new Color(0, 0, 0, 196), m_spriteBatch, msg);
                DrawButton(m_new_game_rect, Color.Gray, m_spriteBatch, newgame);
            }
        }

        private void DrawProductionTriangle()
        {
            // production triangle bg 
            GraphicsDevice.DrawUserPrimitives<VertexPositionColor>(PrimitiveType.TriangleList, m_TriVerts, 0, 1);

            // production triangle text
            float dollars_per_min = 60.0f * me.GetDollarsPerSec();

            Vector2 prod = new Vector2(m_TriTop.X - 80, m_TriTop.Y - 105);
            m_spriteBatch.DrawString(spriteFont, "UNIT PRODUCTION", prod, Color.White);

            string off_cap_msg = "";
            if (me.GetNumOfType(Unit.UnitType.ATTACKER) >= Player.OFF_UNIT_CAP)
                off_cap_msg = "(at unit cap!)";

            string def_cap_msg = "";
            if (me.GetNumOfType(Unit.UnitType.DEFENDER) >= Player.OFF_UNIT_CAP)
                def_cap_msg = "(at unit cap!)";

            string econ_cap_msg = "";
            if (me.GetNumOfType(Unit.UnitType.ECON) >= Player.OFF_UNIT_CAP)
                econ_cap_msg = "(at unit cap!)";

            Vector2 top = new Vector2(m_TriTop.X - 40, m_TriTop.Y - 60);
            float off_per_min = dollars_per_min * me.next_off_frac;
            string off_msg = string.Format("{0:0.} offense {1}\n+ {2:0.} / min", me.GetNumOfType(Unit.UnitType.ATTACKER), off_cap_msg, off_per_min);
            m_spriteBatch.DrawString(spriteFont, off_msg, top, m_TriVerts[0].Color);
            m_spriteBatch.Draw(m_TexSword, new Rectangle((int)m_TriTop.X - 8, (int)m_TriTop.Y - 16, 16, 16), m_TriVerts[0].Color);

            Vector2 left = new Vector2(10, m_TriLeft.Y + 20);
            float def_per_min = dollars_per_min * me.next_def_frac;
            string def_msg = string.Format("{0:0.} defense\n+ {1:0.} / min\n{2}", me.GetNumOfType(Unit.UnitType.DEFENDER), def_per_min, def_cap_msg);
            m_spriteBatch.DrawString(spriteFont, def_msg, left, m_TriVerts[1].Color);
            m_spriteBatch.Draw(m_TexShield, new Rectangle((int)m_TriLeft.X - 20, (int)m_TriLeft.Y, 16, 16), m_TriVerts[1].Color);

            Vector2 right = new Vector2(m_TriRight.X - 40, m_TriRight.Y + 20);
            float econ_per_min = dollars_per_min * me.next_econ_frac;
            string econ_msg = string.Format("{0:0.} economy\n+ {1:0.} / min\n{2}", me.GetNumOfType(Unit.UnitType.ECON), econ_per_min, econ_cap_msg);
            m_spriteBatch.DrawString(spriteFont, econ_msg, right, m_TriVerts[2].Color);
            m_spriteBatch.Draw(m_TexDollar, new Rectangle((int)m_TriRight.X, (int)m_TriRight.Y, 16, 16), m_TriVerts[2].Color);

            Vector2 currEcon = new Vector2(me.next_off_frac * m_TriTop.X + me.next_def_frac * m_TriLeft.X + me.next_econ_frac * m_TriRight.X,
                                           me.next_off_frac * m_TriTop.Y + me.next_def_frac * m_TriLeft.Y + me.next_econ_frac * m_TriRight.Y);
            // offset for the size of the displayed character...
            currEcon.X -= 5;
            currEcon.Y -= 12;

            m_spriteBatch.DrawString(spriteFont, "x", currEcon, Color.White, 0.0f, new Vector2(0, 0), 1.2f, SpriteEffects.None, 0.0f);
            currEcon.X += 1;
            currEcon.Y += 2;
            m_spriteBatch.DrawString(spriteFont, "x", currEcon, Color.Black);
        }
    }
}
